<?php

/**
 * 微信支付接口
 * Author: bluer
 * Date: 5/27/16
 */

require CONFIG_DIR . '/pay.config.php';
require CONFIG_DIR . '/weixin.pay.config.php';

class WeixinPay {
    protected static $_prepayUrl;

    function __construct() {
        self::$_prepayUrl = 'https://api.mch.weixin.qq.com/pay/unifiedorder';
    }

    public static function getPrepayUrl() {
        return self::$_prepayUrl;
    }

    private static function _getPartnerKey($attach) {
        switch ($attach) {
            case 'dailyplan':
                return WXPAY_DAILYPLAN_PARTNER_KEY;

            case 'dailylearn':
                return WXPAY_DAILYLEARN_PARTNER_KEY;

            case 'quizbank':
                return WXPAY_QUIZBANK_PARTNER_KEY;

            case 'gongzhonghao':
                return WXPAY_HAO_PARTNER_KEY;

            default:
                return '';
        }
    }

    /**
     * 生成要请求给微信的参数数组
     * @param array $para 请求前的参数数组
     * @return array 要请求的参数数组
     */
    private function _buildRequestPara($para) {

        //对待签名参数数组排序
        ksort($para);

        $str = $this->_array2query($para);
        //生成签名结果
        $sign = strtoupper(md5($str));

        //签名结果与签名方式加入请求提交参数组中
        $para['sign'] = $sign;

        return $para;
    }

    /**
     * 产生随机字符串，不长于32位
     * @param int $length
     * @return string 产生的随机字符串
     */
    private static function _getNonceStr($length = 32) {
        $chars = "abcdefghijklmnopqrstuvwxyz0123456789";
        $str ="";
        for ( $i = 0; $i < $length; $i++ )  {
            $str .= $chars{mt_rand(0, strlen($chars)-1)};
        }
        return $str;
    }

    /**
     * 获取支付需要的数据 (该支付方式似乎未进行实际应用,要使用需进行具体测试)
     * @param int $orderId 订单号
     * @param string $productName 订单名称
     * @param float $price 价格
     * @return mixed
     */
    public function qrWebPay($orderId, $productName, $price) {
        //构造预支付请求参数
        $para = array(
            'appid' => WX_WEB_APP_ID,
            'mch_id' => WX_WEB_PARTNER_ID,
            'nonce_str' => self::_getNonceStr(),
            'body' => $productName,
            'out_trade_no' => $orderId,
            'product_id' => $orderId,
            'total_fee' => intval($price*100),
            'spbill_create_ip' => $_SERVER['REMOTE_ADDR'],
            'notify_url' => WX_WEB_QR_NOTIFY_URL,
            'trade_type' => 'NATIVE'
        );

        //生成最终的请求参数数组
        $para = $this->_buildRequestPara($para);

        //xml格式化
        $xmlStr = $this->array2xml($para);

        //获取请求结果
        $result = $this->_postXmlCurl($xmlStr);

        if (empty($result))
            return '';

        return $result['code_url'];
    }

    /**
     * 签名算法
     *
     * @param $params
     * @param $partnerKey
     * @return string
     */
    public function sign($params, $partnerKey = '') {
        ksort($params);

        $str = '';
        foreach ($params as $key => $value) {
            $str .= ($key.'='.$value.'&');
        }

        if ($partnerKey == '') {
            $str .= 'key=' . self::_getPartnerKey($params['attach']);
        } else {
            $str .= 'key=' . $partnerKey;
        }

        return strtoupper(md5($str));
    }

    public function verifyReturn($params, $partnerKey = '') {
        if (!isset($params['sign'])) return false;

        $sign = $params['sign'];
        unset($params['sign']);

        return $sign == $this->sign($params, $partnerKey);
    }

    /**
     * 检验回调有效性
     * @param array $para 参数
     * @return mixed
     */
    function checkSign($para) {
        $sign = $para['sign'];
        unset($para['sign']);

        //对待签名参数数组排序
        ksort($para);

        $str = $this->_array2query($para);

        return $sign == strtoupper(md5($str));
    }

    /**
     * xml字符串转参数数组
     * @param string $xml xml字符串
     * @return string|bool
     */
    public function xml2array($xml) {
        $result = simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA);
        if (!empty($result) && is_object($result)) return (array)$result;

        return false;
    }

    /**
     * xml字符串转参数数组 (疑问: 为什么和上面不能统一?)
     * @param string $xml xml字符串
     * @return string
     */
    public function xml2arrayForQrWeb($xml) {
        $result = simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA);
        $result = get_object_vars($result);

        if (!$this->checkSign($result))
            return array();

        return $result;
    }

    /**
     * 构造参数请求xml字符串
     * @param array $para 参数数组
     * @return string
     */
    public function array2xml($para) {
        $xml = "<xml>";
        foreach ($para as $key => $val)
        {
            if (is_numeric($val)){
                $xml .= "<".$key.">".$val."</".$key.">";
            }else{
                $xml .= "<".$key."><![CDATA[".$val."]]></".$key.">";
            }
        }
        $xml .= "</xml>";

        return $xml;
    }

    /**
     * 数组转成请求字符串
     * @param array $para 参数数组
     * @param bool $urlencode 是否对参数进行url编码
     * @return string
     */
    private function _array2query($para, $urlencode = false) {
        $str = '';
        foreach ($para as $key => $value) {
            $value2 = $urlencode ? urlencode($value) : $value;
            $str .= ($key.'='.$value2.'&');
        }
        $str .= 'key='.WX_WEB_PARTNER_KEY;

        return $str;
    }

    /**
     * 发送xml信息到微信接口
     * @param string $xml xml字符串
     * @return mixed
     */
    private function _postXmlCurl($xml) {
        $ch = curl_init();
        curl_setopt($ch,CURLOPT_URL, self::$_prepayUrl);
        curl_setopt($ch, CURLOPT_HEADER, false);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

        curl_setopt($ch, CURLOPT_POST, TRUE);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
        $data = curl_exec($ch);
        curl_close($ch);

        if (empty($data))
            return array();

        $data = $this->xml2arrayForQrWeb($data);

        return $data;
    }
}
